/* 
 *  Syntelos XB
 *  Copyright (C) 2006 John Pritchard.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307 USA
 */

package syntelos.xb;

/** 
 * <p> This {@link Type} is a "top level class" for reading and
 * writing {@link CF} and {@link XB} formats via this DOM. </p>
 * 
 * @see syntelos.xb.Type$Class
 * 
 * @author John Pritchard (jdp@syntelos.com)
 */
public class Class 
    extends Type
{
    public final static syntelos.iou.dom.Name NAME = new syntelos.iou.dom.impl.Name(ELEMENT_NODE,"http://www.syntelos.org/xb","xb:class");

    private final static int HEADLINE = 0xcafebabe;



    private CF.Constants constants = new CF.Constants();

    private java.lang.Class jclass;

    public Class(org.w3c.dom.Document doc, syntelos.iou.dom.Name name){
        super(doc,name);
    }
    public Class(org.w3c.dom.Document doc){
        this(doc,NAME);
    }
    /**
     * <p> Construct a new 'class' object with its requisite document
     * container. </p>
     */
    public Class(){
        this(new syntelos.iou.dom.impl.Document());
        org.w3c.dom.Document doc = this.getOwnerDocument();
        doc.appendChild(this);
    }

    /**
     * <p> Construct basic empty document for DOM programmers. </p>
     */
    public void init(){
        this.getVersion().init();
        this.getConstants();
        this.getAccess();
        this.getType();
        this.getSuper();
        this.getInterfaces();
        this.getFields();
        this.getMethods();
        this.getAttributesChild();
    }
    public void init(java.lang.String name){
        this.init();
        if (null != name && 0 < name.length()){
            while (0 < name.length() && '/' == name.charAt(0))
                name = name.substring(1);
            this.getType().setChildText(syntelos.iou.chbuf.cat("class:utf8:",name.replace('.','/')));
        }
    }
    public java.lang.Class getJClass(){
        return this.jclass;
    }
    protected void setJClass(java.lang.Class jclass){
        this.jclass = jclass;
    }
    public boolean isInterface(){
        Access access = this.getAccess(false);
        if (null == access)
            return true;
        else
            return access.isInterface();
    }
    public final CF.Constants getConstants(){

        return this.constants;
    }
    public final syntelos.xb.Version getVersion(){
        return this.getVersion(true);
    }
    public final syntelos.xb.Version getVersion(boolean create){
        syntelos.xb.Version version = (syntelos.xb.Version)this.getChildByName("version");
        if (null == version && create){
            version = new syntelos.xb.Version();
            this.appendChild(version);
        }
        return version;
    }
    public final Access getAccess(){
        return this.getAccess(true);
    }
    public final Access getAccess(boolean create){
        Access access = (Access)this.getChildByName("access");
        if (null == access && create){
            access = new Access();
            this.appendChild(access);
        }
        return access;
    }
    public final Type.Class getType(){
        return this.getType(true);
    }
    public final Type.Class getType(boolean create){
        Type.Class type = (Type.Class)this.getChildByName("type.class");
        if (null == type && create){
            type = new Type.Class();
            this.appendChild(type);
        }
        return type;
    }
    /**
     * @return This class name in native (slash) format, or null if
     * unknown
     */
    public java.lang.String getClassname(){
        Type.Class clas = this.getType(false);
        if (null == clas)
            return null;
        else {
            CF.Constants.Class cclas = clas.getTclassConst();
            if (null == cclas)
                return null;
            else 
                return cclas.getNameString();
        }
    }
    public Super getSuper(){
        return this.getSuper(true);
    }
    public Super getSuper(boolean create){
        Super sname = (Super)this.getChildByName("super");
        if (null == sname && create){
            sname = new Super();
            this.appendChild(sname);
        }
        return sname;
    }
    public Interfaces getInterfaces(){
        return this.getInterfaces(true);
    }
    public Interfaces getInterfaces(boolean create){
        Interfaces interfaces = (Interfaces)this.getChildByName("interfaces");
        if (null == interfaces && create){
            interfaces = new Interfaces();
            this.appendChild(interfaces);
        }
        return interfaces;
    }
    public Fields getFields(){
        return this.getFields(true);
    }
    public Fields getFields(boolean create){
        Fields fields = (Fields)this.getChildByName("fields");
        if (null == fields && create){
            fields = new Fields();
            this.appendChild(fields);
        }
        return fields;
    }
    public Methods getMethods(){
        return this.getMethods(true);
    }
    public Methods getMethods(boolean create){
        Methods methods = (Methods)this.getChildByName("methods");
        if (null == methods && create){
            methods = new Methods();
            this.appendChild(methods);
        }
        return methods;
    }
    /**
     * @param uri Field or Method reference
     * @return True of the reference points to a member of this class.
     * An interface method reference will return true if the reference
     * could refer to a member of this class.
     */
    public boolean isMember(Uri uri){
        if (null == uri)
            return false;
        else {
            java.lang.String th_classname = this.getClassname();
            if (null == th_classname)
                throw new Error.State("Missing this classname.");
            else {
                java.lang.String re_classname = uri.getPath();
                if (null == re_classname)
                    throw new Error.State("Missing reference classname in '"+uri.toString()+"'.");
                else {
                    java.lang.String re_name = uri.getQuery("name");
                    if (null == re_name)
                        throw new Error.State("Missing reference name in '"+uri.toString()+"'.");
                    else {
                        Uri re_name_uri = new Uri(re_name);
                        re_name = re_name_uri.getPath();
                        if (null == re_name)
                            throw new Error.State("Missing reference name value in '"+uri.toString()+"'.");
                        else {
                            switch (CF.Constants.Scheme(uri)){
                            case CF.Constants.FIELDREF:
                                if (th_classname.equals(re_classname)){
                                    Fields fields = this.getFields(false);
                                    if (null == fields)
                                        throw new Error.State("Missing fields.");
                                    else {
                                        Field field = fields.getField(uri);
                                        return (null != field);
                                    }
                                }
                                else
                                    return false;

                            case CF.Constants.METHODREF:
                                if (th_classname.equals(re_classname)){
                                    Methods methods = this.getMethods(false);
                                    if (null == methods)
                                        throw new Error.State("Missing methods.");
                                    else {
                                        Method method = methods.getMethod(uri);
                                        return (null != method);
                                    }
                                }
                                else
                                    return false;

                            case CF.Constants.IMETHODREF:
                                /*
                                 * check interfaces (or if this is an interface)
                                 */
                                throw new Error.Bug("todo");

                            default:
                                throw new Error.Argument(uri.toString());
                            }
                        }
                    }
                }
            }
        }
    }
    public final Attributes getAttributesChild(){
        return this.getAttributesChild(true);
    }
    public final Attributes getAttributesChild(boolean create){
        Attributes attributes = (Attributes)this.getChildByName("attributes");
        if (null == attributes && create){
            attributes = new Attributes();
            this.appendChild(attributes);
        }
        return attributes;
    }
    public syntelos.xb.at.SourceFile getAttributeSourceFile(){
        return this.getAttributesChild().getSourceFile();
    }
    /**
     * @return Always 'this' as a JVM class is a class file unit,
     * inner or not.  The symbolic term 'declaring' is shared with
     * {@link Method} and {@link Field} and other classes in this
     * package where its meaning is less awkward.  Obviously we could
     * not have chosen to use the symbol <code>'getClass'</code>.
     */
    public final syntelos.xb.Class getDeclaringClass(){
        return this;
    }

    public void readCF(IO.Uri src, CF.Reader reader)
        throws java.io.IOException
    {
        if (HEADLINE == reader.readInt()){
            this.getVersion().readCF(src,reader);
            this.getConstants().readCF(src,reader);
            this.getAccess().readCF(src,reader);
            this.getType().readCF(src,reader);
            this.getSuper().readCF(src,reader);
            this.getInterfaces().readCF(src,reader);
            this.getFields().readCF(src,reader);
            this.getMethods().readCF(src,reader);
            this.getAttributesChild().readCF(src,reader);
            //this.decompile()//
        }
        else
            throw new Error.Format("Magic '0xCAFEBABE' not found.");
    }
    /**
     * <p> Calls {@link syntelos.xb.Base#compile()} before
     * writing class file format code to the writer target
     * stream. </p>
     */
    public void writeCF(IO.Uri dst, CF.Writer writer)
        throws java.io.IOException
    {
        //this.compile()//
        writer.writeInt(HEADLINE);
        this.getVersion().writeCF(dst,writer);
        this.getConstants().writeCF(dst,writer);
        this.getAccess().writeCF(dst,writer);
        this.getType().writeCF(dst,writer);
        this.getSuper().writeCF(dst,writer);
        this.getInterfaces().writeCF(dst,writer);
        this.getFields().writeCF(dst,writer);
        this.getMethods().writeCF(dst,writer);
        this.getAttributesChild().writeCF(dst,writer);
    }
    /**
     * <p> Read XB XML from input stream using platform JAX/SAX
     * parser and return new class object. </p>
     * 
     * @param src Input stream source reference
     * @param reader Source input stream contains XB XML
     * @return New {@link Class} object in new document read from
     * input, and decompiled.
     */
    public static syntelos.xb.Class readXB(IO.Uri src, java.io.InputStream reader)
        throws java.io.IOException
    {
        try {
            syntelos.iou.dom.Document doc = new syntelos.iou.dom.impl.Document();
            syntelos.iou.dom.Builder.Parser.Parse(reader,doc,src.toString());
            syntelos.xb.Class nclass = (syntelos.xb.Class)doc.getDocumentElement();
            //nclass.decompile()//
            return nclass;
        }
        catch (org.xml.sax.SAXException sax){
            Error err = new Error.Format();
            err.initCause(sax);
            throw err;
        }
    }
    /**
     * <p> Write XB XML to output stream using {@link
     * syntelos.iou.dom.Formatter$Writer#Write}. </p>
     * 
     * @param dst Output stream target reference
     * @param write Target output stream for XB XML
     */
    public void writeXB(IO.Uri dst, java.io.OutputStream writer)
        throws java.io.IOException
    {
        //this.compile()//
        syntelos.iou.dom.Formatter.User.Write(this,writer);
    }
}
